import {
  TAG_ROOT,
  TAG_HOST,
  TAG_TEXT,
  ELEMENT_TEXT,
  PLACEMENT,
  DELETION,
  UPDATE,
  TAG_CLASS,
  TAG_FUNCTION_COMPONENT,
} from './constants';
import { setProps } from './utils';
import { UpdateQueue, Update } from './updateQueue';

let nextUnitOfWork = null; // 下一个工作单元
let workInProgressRoot = null; // rootFiber应用的根
let currentRoot = null;
let deletions = []; // 存储要删除的节点

let workInProgressFiber = null;
let hookIndex = 0;
function scheduleRoot(root) {
  /* 
    root = {
      type: TAG_ROOT,
      stateNode: container,
      props: { children: [element]}
    }
  */
  // 实现双缓存树，即始终只有两棵树存在，防止频繁创建对象导致gc频繁
  if (currentRoot && currentRoot.alternate) {
    // 第三次更新及以后更新 交替使用A b树
    workInProgressRoot = currentRoot.alternate; // 复用A树
    workInProgressRoot.alternate = currentRoot; //
    if (root) {
      workInProgressRoot.props = root.props; // 更新到最新的虚拟的dom
    }
  } else if (currentRoot) {
    // 第二次更新，还需要在创建一个B树
    if (root) {
      workInProgressRoot = root;
      workInProgressRoot.alternate = currentRoot;
    } else {
      // 直接复用currentRoot
      workInProgressRoot = {
        ...currentRoot,
        alternate: currentRoot,
      };
    }
  } else {
    // 第一次更新，需要根据虚拟dom创建一颗A树
    workInProgressRoot = root;
  }
  workInProgressRoot.firstEffect = workInProgressRoot.lastEffect = workInProgressRoot.nextEffect = null;
  nextUnitOfWork = workInProgressRoot;
}

function workLoop(deadline) {
  let shouldYield = false;
  while (!shouldYield && nextUnitOfWork) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline.timeRemaining() < 1;
  }

  if (!nextUnitOfWork && workInProgressRoot) {
    console.log('进入commit阶段');
    commitRoot();
  }
  requestIdleCallback(workLoop, { timeout: 500 });
}

// 开始工作
function performUnitOfWork(currentFiber) {
  beginWork(currentFiber);
  if (currentFiber.child) {
    return currentFiber.child;
  }

  while (currentFiber) {
    // 到达这里说明currentFiber节点已经完成自身的子fiber节点创建工作和dom创建
    // 需要构建副作用链条 effect list
    completeUnitOfWork(currentFiber);
    if (currentFiber.sibling) {
      return currentFiber.sibling;
    }
    currentFiber = currentFiber.return;
  }
}
/**
 * 有两个作用：
 * 1 创建currentFiber的子fiber节点
 * 2 创建dom节点
 * @param {*} currentFiber
 */
function beginWork(currentFiber) {
  // 判断不同类型的节点进行对应的操作
  switch (currentFiber.tag) {
    case TAG_ROOT: {
      updateHostRoot(currentFiber);
      break;
    }
    case TAG_TEXT: {
      updateHostText(currentFiber);
      break;
    }
    case TAG_HOST: {
      updateHost(currentFiber);
      break;
    }
    case TAG_CLASS: {
      updateClassComponent(currentFiber);
      break;
    }
    case TAG_FUNCTION_COMPONENT: {
      updateFunctionComponent(currentFiber);
      break;
    }
    default:
      break;
  }
  // 1 创建
}

function reconcileChildren(currentFiber, newChildren) {
  let index = 0;
  let prevFiber = null;
  // 其实oldFiber的stateNode对应的就是当前屏幕上显示的dom，不能修改它
  // 拿到旧树中的第一个子节点，方便下面对比复用
  let oldFiber = currentFiber.alternate && currentFiber.alternate.child;
  if (oldFiber)
    oldFiber.firstEffect = oldFiber.lastEffect = oldFiber.nextEffect = null;
  while (index < newChildren.length || oldFiber) {
    let newFiber;
    // newChild可能是null
    const newChild = newChildren[index];
    let tag;
    if (newChild && newChild.type === ELEMENT_TEXT) {
      tag = TAG_TEXT;
    } else if (newChild && typeof newChild.type === 'string') {
      tag = TAG_HOST;
    } else if (
      newChild &&
      typeof newChild.type === 'function' &&
      newChild.type.prototype.isReactComponent
    ) {
      tag = TAG_CLASS;
    } else if (newChild && typeof newChild.type === 'function') {
      tag = TAG_FUNCTION_COMPONENT;
    }
    const sameType = oldFiber && newChild && oldFiber.type === newChild.type;

    if (sameType) {
      // 双缓存，可以复用fiber
      if (oldFiber.alternate) {
        newFiber = oldFiber.alternate;
        newFiber.props = newChild.props;
        newFiber.effectTag = UPDATE;
        newFiber.nextEffect = null;
        newFiber.alternate = oldFiber;
        newFiber.updateQueue = oldFiber.updateQueue || new UpdateQueue();
      } else {
        // 可以复用dom
        newFiber = {
          tag: oldFiber.tag,
          type: oldFiber.type,
          props: newChild.props,
          stateNode: oldFiber.stateNode,
          return: currentFiber,
          // 一定要记得设置alternate，否则二次更新的时候，每次都是认为新节点，执行插入操作
          alternate: oldFiber,
          nextEffect: null,
          effectTag: UPDATE,
          updateQueue: oldFiber.updateQueue || new UpdateQueue(),
        };
      }
    } else {
      if (newChild) {
        // 新增
        newFiber = {
          tag,
          type: newChild.type,
          props: newChild.props,
          stateNode: null,
          return: currentFiber,
          nextEffect: null,
          effectTag: PLACEMENT,
          updateQueue: new UpdateQueue(),
        };
      }
      if (oldFiber) {
        // 需要删除节点
        oldFiber.effectTag = DELETION;
        deletions.push(oldFiber);
      }
    }

    if (newFiber) {
      if (index === 0) {
        currentFiber.child = newFiber;
      } else {
        prevFiber.sibling = newFiber;
      }
    }

    if (oldFiber) {
      oldFiber = oldFiber.sibling;
    }
    prevFiber = newFiber;
    index++;
  }
}

function updateFunctionComponent(currentFiber) {
  workInProgressFiber = currentFiber;
  hookIndex = 0;
  currentFiber.hooks = [];
  const children = [currentFiber.type(currentFiber.props)];
  reconcileChildren(currentFiber, children);
}

function updateClassComponent(currentFiber) {
  if (!currentFiber.stateNode) {
    currentFiber.stateNode = new currentFiber.type(currentFiber.props);
    currentFiber.updateQueue = new UpdateQueue();
    // 指针指向fiber
    currentFiber.stateNode.internalFiber = currentFiber;
  }
  // 渲染时更新state值到最新的值
  currentFiber.stateNode.state = currentFiber.updateQueue.forceUpdate(
    currentFiber.stateNode.state
  );
  const children = [currentFiber.stateNode.render()];
  reconcileChildren(currentFiber, children);
}
function updateHostRoot(currentFiber) {
  const newChildren = currentFiber.props.children;
  reconcileChildren(currentFiber, newChildren);
}

function updateHostText(currentFiber) {
  if (!currentFiber.stateNode) {
    currentFiber.stateNode = createDom(currentFiber);
  }
}
function updateHost(currentFiber) {
  if (!currentFiber.stateNode) {
    currentFiber.stateNode = createDom(currentFiber);
  }
  const newChildren = currentFiber.props.children;
  reconcileChildren(currentFiber, newChildren);
}

function createDom(fiber) {
  if (fiber.tag === TAG_TEXT) {
    return document.createTextNode(fiber.props.text);
  } else if (fiber.tag === TAG_HOST) {
    const stateNode = document.createElement(fiber.type);
    updateDom(stateNode, {}, fiber.props);
    return stateNode;
  }
}

function updateDom(stateNode, oldProps, newProps) {
  if (stateNode && stateNode.setAttribute) {
    setProps(stateNode, oldProps, newProps);
  }
}

// 构建effect list单链表
function completeUnitOfWork(currentFiber) {
  const returnFiber = currentFiber.return;
  if (returnFiber) {
    // 将子fiber节点的firstEffect和lastEffect挂在returnFiber上
    if (!returnFiber.firstEffect) {
      // 将子节点的firstEffect挂在父节点上
      returnFiber.firstEffect = currentFiber.firstEffect;
    }
    if (currentFiber.lastEffect) {
      if (returnFiber.lastEffect) {
        // 构建链条，将父节点的lastEffect.nextEffect指向currentFiber.firstEffect
        // 作用就是把父子节点的两个链表连接起来！
        returnFiber.lastEffect.nextEffect = currentFiber.firstEffect;
      }
      // 将子节点的lastEffect挂在到父节点上
      returnFiber.lastEffect = currentFiber.lastEffect;
    }

    // 将currentFiber挂在effectlist 单链表上
    if (currentFiber.effectTag) {
      if (returnFiber.lastEffect) {
        // 构建链条关系
        returnFiber.lastEffect.nextEffect = currentFiber;
      } else {
        returnFiber.firstEffect = currentFiber;
      }

      // 父节点的lastEffect始终指向当前节点
      returnFiber.lastEffect = currentFiber;
    }
  }
}

function commitRoot() {
  // 先处理删除的节点
  deletions.forEach(commitWork);
  let currentFiber = workInProgressRoot.firstEffect;
  while (currentFiber) {
    commitWork(currentFiber);
    currentFiber = currentFiber.nextEffect;
  }
  currentRoot = workInProgressRoot;
  deletions = [];
  workInProgressRoot = null;
}

function commitDeletion(parentDom, currentFiber) {
  if (currentFiber.tag === TAG_HOST || currentFiber.tag === TAG_TEXT) {
    parentDom.removeChild(currentFiber.stateNode);
  } else {
    commitDeletion(parentDom, currentFiber.child);
  }
}

// 处理fiber的完成阶段
function commitWork(currentFiber) {
  if (!currentFiber) return;
  let returnFiber = currentFiber.return;
  // 遇到类组件的时候，stateNode不是dom节点，需要递归往上找
  while (
    returnFiber.tag !== TAG_HOST &&
    returnFiber.tag !== TAG_ROOT &&
    returnFiber.tag !== TAG_TEXT
  ) {
    returnFiber = returnFiber.return;
  }
  const parentDom = returnFiber.stateNode;
  if (currentFiber.effectTag === PLACEMENT) {
    // currentFiber.stateNode可能不是原生节点，需要向下查找
    let nextFiber = currentFiber;
    while (nextFiber.tag !== TAG_HOST && nextFiber.tag !== TAG_TEXT) {
      nextFiber = nextFiber.child;
    }
    parentDom.appendChild(nextFiber.stateNode);
  } else if (currentFiber.effectTag === DELETION) {
    commitDeletion(parentDom, currentFiber);
  } else if (currentFiber.effectTag === UPDATE) {
    if (currentFiber.type === ELEMENT_TEXT) {
      if (currentFiber.alternate.props.text !== currentFiber.props.text) {
        currentFiber.stateNode.textContent = currentFiber.props.text;
      }
    } else {
      updateDom(
        currentFiber.stateNode,
        currentFiber.alternate.props,
        currentFiber.props
      );
    }
  }
  // 需要重置，否则在下次复用的时候可能被混乱
  currentFiber.effectTag = null;
}

export function useReducer(reducer, initData) {
  // 必须先拿到上次的hooks进行更新操作，因为在dispatch函数内收集了数据
  let currHook =
    workInProgressFiber.alternate &&
    workInProgressFiber.alternate.hooks &&
    workInProgressFiber.alternate.hooks[hookIndex];
  if (currHook) {
    // 更新阶段
    currHook.state = currHook.updateQueue.forceUpdate(currHook.state);
  } else {
    // 初始化阶段
    currHook = {
      state: initData,
      updateQueue: new UpdateQueue(),
    };
  }
  const dispatch = (action) => {
    const state =
      typeof reducer === 'function' ? reducer(currHook.state, action) : action;
    currHook.updateQueue.enqueueUpdate(new Update(state));
    // 更新
    scheduleRoot();
  };
  workInProgressFiber.hooks[hookIndex++] = currHook;
  return [currHook.state, dispatch];
}

export function useState(initDate) {
  return useReducer(null, initDate);
}
requestIdleCallback(workLoop, { timeout: 500 });
export { scheduleRoot };
